/*
 *  Gurr - A multiprotocol chat client for Android
 *  Copyright (C) 2011 Malte Aschermann, Wilfried Ramaker
 *  
 *  This file is part of Gurr.
 *  
 *  Gurr is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  Gurr is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with Gurr.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.masc.gurr;

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.content.Intent;

import com.masc.gurr.model.Account;
import com.masc.gurr.model.AccountMap;
import com.masc.gurr.model.Contact;
import com.masc.gurr.model.Group;
import com.masc.gurr.model.History;
import com.masc.gurr.model.ProtocolType;
import com.masc.gurr.model.Session;
import com.masc.gurr.model.xml.Reader;
import com.masc.gurr.model.xml.Writer;
import com.masc.gurr.net.ICQ;
import com.masc.gurr.view.activities.AccountActivity;
import com.masc.gurr.view.activities.ChatWindowActivity;
import com.masc.gurr.view.activities.ChatsActivity;
import com.masc.gurr.view.activities.ContactsActivity;
import com.masc.gurr.view.activities.GurrActivity;


public class Controller {
	/* Singleton */
	private static Controller instance;
	
	/* classes */
	private AccountActivity accountActivity;
	private ChatsActivity chatsActivity;
	private ContactsActivity contactsActivity;
	private ChatWindowActivity chatWindowActivity;
	
	/* variables */
	private Group fGroupContacts;
	
	/* list of contacts we are currently chatting with */
	private List<Contact> chatListItems;
	
//	private Map<String, Contact> fGroupContacts.getMap();

	private List<Group> fLstGroups;
	
	private Writer writer;
	private Reader reader;
	
	/*
	 * stores the current history for each contact
	 */
	private History fCurrentHistory;
	
	/* buffer history to be written. will be cleared after write */
	private History fBufferHistory;
	
	
	
	private String focusedUin;
	
	private AccountMap fMapUinMyAccounts;
	
	
	/* timer for updating/incrementing idle times */
	private Timer fIdleTimer;
	
	ICQ icq;

	private GurrActivity gurrActivity;
	
	private Group fServerRootGroup;
	private Group fStoredRootGroup;
	
	private Controller() {
		writer = new Writer();
		reader = new Reader();
		fLstGroups = new ArrayList<Group>();
		chatListItems = new ArrayList<Contact>();
		fServerRootGroup = fStoredRootGroup = fGroupContacts = new Group("/");
		fMapUinMyAccounts = new AccountMap();
		
		fIdleTimer = new Timer();
	}
	
	public void connectICQ(String login, String pass){
		icq = new ICQ();
		icq.connect(login, pass);
		
		fBufferHistory = new History(History.TYPE_BUFFER, login);
		long freeSid = reader.getFreeSid(gurrActivity, getMyAccount().getUin());
		if (freeSid != -1) {
			fCurrentHistory.setFreeSid(freeSid);
			fBufferHistory.setFreeSid(freeSid);
		}			
	}
	
	public void readStoredAccounts() {
		fMapUinMyAccounts = reader.readAccounts(accountActivity);
	}
	
	public void disconnectICQ(){
		
		icq.getBosConn().disconnect();
		chatListItems.clear();
		focusedUin = null;
		
		fGroupContacts.getMap().clear();
	}

	public AccountActivity getAccountActivity() {
		return accountActivity;
	}

	public void setAccountActivity(AccountActivity accountActivity) {
		this.accountActivity = accountActivity;
	}

	public ChatsActivity getChatsActivity() {
		return chatsActivity;
	}

	public void setChatsActivity(ChatsActivity chatsActivity) {
		this.chatsActivity = chatsActivity;
	}

	public void setGurrActivity(GurrActivity gurrActivity) {
		this.gurrActivity = gurrActivity;
	}
	
	public ContactsActivity getContactsActivity() {
		return contactsActivity;
	}

	public void setContactsActivity(ContactsActivity contactsActivity) {
		this.contactsActivity = contactsActivity;
	}

	public static Controller getInstance() {
		if (instance == null)
			instance = new Controller();
		return instance;
	}

	public void chatWith(String uid) {
		focusedUin = uid;
				
		Intent i = new Intent(accountActivity, ChatWindowActivity.class);
        accountActivity.startActivity(i);
        // the results are called on widgetActivityCallback
	}
	
	private void addMsgToHistory(String pStrFromUin, String pStrBuddyUin, String msgStr) {
		// add new session to current history if there is none
		if (! fCurrentHistory.hasSession(pStrBuddyUin)) {
			fCurrentHistory.addSession(pStrBuddyUin, fMapUinMyAccounts.getSelectedAccount().getUin(), ProtocolType.ICQ);
		}
		// add message to last/latest session w/ buddy
		int last = fCurrentHistory.getBuddyUinSessionMap().get(pStrBuddyUin).size()-1;
		fCurrentHistory.getBuddyUinSessionMap().get(pStrBuddyUin).get(last).addMessage(pStrFromUin, msgStr, new Date());
		
		/* do the same for the buffer history */
		if (! fBufferHistory.hasSession(pStrBuddyUin)) {
			fBufferHistory.addSession(pStrBuddyUin, fMapUinMyAccounts.getSelectedAccount().getUin(), ProtocolType.ICQ);
		}
		last = fBufferHistory.getBuddyUinSessionMap().get(pStrBuddyUin).size()-1;
		fBufferHistory.getBuddyUinSessionMap().get(pStrBuddyUin).get(last).addMessage(pStrFromUin, msgStr, new Date());
		
	}
		
	public void addNewChatLine(String pStrBuddyUin, String msg) {
		
		/* create new session if necessary */
		if (!chatListItems.contains(fGroupContacts.getMap().get(focusedUin)))
			chatListItems.add(fGroupContacts.getMap().get(focusedUin));
		if (chatsActivity != null)
			chatsActivity.update(chatListItems);
		
		addMsgToHistory(fMapUinMyAccounts.getSelectedAccount().getUin(), pStrBuddyUin, msg);
		
		if(chatWindowActivity != null){
			chatWindowActivity.update();
			/** TODO: OTR layer */
			icq.sendIM(focusedUin, msg);
		}
	}

	@Deprecated
	public List<Contact> getChatItems() {
		return chatListItems;
	}
	
	@Deprecated
	public void setChatItems(List<Contact> items) {
		chatListItems=items;
	}

	public Group getContacts() {
		return fGroupContacts;
	}

	/**
	 * Close current active session, remove chat from chatlist/chatsactivity and write current session to xml
	 * @param pStrBuddyUin
	 */
	public void removeChat(String pStrBuddyUin) {
		chatListItems.remove(fGroupContacts.getMap().get(pStrBuddyUin));
		if (chatsActivity != null)
			chatsActivity.update(chatListItems);
		
		/* write session */
		writeSessionWith(pStrBuddyUin);
	}

	private void writeSessionWith(String pStrBuddyUin) {
		writer.writeHistory(fBufferHistory, pStrBuddyUin, gurrActivity);
	}

	public void sendIcqIM(String sid, String msg) {
		if (icq != null)
			icq.sendIM(sid, msg);
	}
	
	//handle incomming ICQ-msgs
	public void getIcqIm(final String pStrBuddyUin, final String msgStr) {
		
		/* create new session if necessary */
		if (!chatListItems.contains(fGroupContacts.getMap().get(pStrBuddyUin)))
			chatListItems.add(fGroupContacts.getMap().get(pStrBuddyUin));
		
		if(chatsActivity != null){
			Runnable run = new Runnable(){
				public void run() {
					chatsActivity.update(chatListItems);
				}
			};
			chatsActivity.runOnUiThread(run);
		}
			
		addMsgToHistory(pStrBuddyUin, pStrBuddyUin, msgStr);
		
		if(chatWindowActivity != null){
			Runnable run = new Runnable(){

				public void run() {
					chatWindowActivity.update();
				}
			};
			chatWindowActivity.runOnUiThread(run);
		}
		
		

	}

	public String getFocusedUid() {
		return focusedUin;
	}

	public void setFocusedUid(String focusedUid) {
		this.focusedUin = focusedUid;
	}

	public ChatWindowActivity getChatWindowActivity() {
		return chatWindowActivity;
	}

	public void setChatWindowActivity(ChatWindowActivity chatWindowActivity) {
		this.chatWindowActivity = chatWindowActivity;
	}

	/**
	 * Return Session object w/ pStrBuddyUin 
	 * @param pStrBuddyUin uin of buddy
	 * @return session
	 */
	public Session getCurrentSession(String pStrBuddyUin, ProtocolType pProtocolType) {
		if (! fCurrentHistory.getBuddyUinSessionMap().containsKey(pStrBuddyUin))
			fCurrentHistory.addSession(pStrBuddyUin, fMapUinMyAccounts.getSelectedAccount().getUin(), pProtocolType);
		
		// current session => last entry of session list
		int last = fCurrentHistory.getBuddyUinSessionMap().get(pStrBuddyUin).size()-1;
		return fCurrentHistory.getBuddyUinSessionMap().get(pStrBuddyUin).get(last);
	}


	public ArrayList<Session> getAllSessions(String pStrBuddyUin, ProtocolType pProtocolType) {
		if (! fCurrentHistory.getBuddyUinSessionMap().containsKey(pStrBuddyUin))
			fCurrentHistory.addSession(pStrBuddyUin, fMapUinMyAccounts.getSelectedAccount().getUin(), pProtocolType);
		
		return fCurrentHistory.getBuddyUinSessionMap().get(pStrBuddyUin);
	}
	
	public Account getMyAccount() {
		return fMapUinMyAccounts.getSelectedAccount();
	}
	
	/** change contact status 
	 * @param idleMins 
	 * @param statusMsg */
	public void icqChangeContactStatus(String sn, long status, int idleMins, String statusMsg) {
		
		Contact cont = fGroupContacts.getMap().get(sn);
		
		if (cont == null)
			return;
		
		cont.setStatus(status);
		cont.setStatusMsg(statusMsg);

		if (idleMins != -1) { /* set idle time and start timer */
			cont.setIdleMins(idleMins);
			fIdleTimer.schedule(cont.getNewIdleTimerTask(), 60, 1000*60);
		}
		else { /* remove ex. timertask */

			if (cont.getIdleTimerTask() != null) {
				System.out.println("Controller: removing existing idle timer of " + cont.getUin() + " (" + cont.getName() +")");

				cont.getIdleTimerTask().cancel();
				fIdleTimer.purge();
				cont.setIdleTimerTask(null);
			}
		}
		
		if (contactsActivity != null){
			Runnable run = new Runnable(){
				public void run(){
					contactsActivity.onContentChanged();
				}
			};
			contactsActivity.runOnUiThread(run);
		}
	}


	public ICQ getIcq() {
		return icq;
	}


	public void setIcq(ICQ icq) {
		this.icq = icq;
	}
	
	/** (re)write xml contact list after changes/etc/... */
	public void writeContactList() {
		if (fMapUinMyAccounts.getSelectedAccount().getUin() != null && fGroupContacts != null && gurrActivity != null)
			writer.writeContactList(fMapUinMyAccounts.getSelectedAccount(), ProtocolType.ICQ, fGroupContacts, gurrActivity);
	}
	
	/** load xml contact list into fGroupContacts **/
	public void loadContactList() {
		if (fMapUinMyAccounts.getSelectedAccount() == null) {
			fGroupContacts = new Group("/");
			return;
		}
		
		Group g = reader.readContactList(fMapUinMyAccounts.getSelectedAccount(), ProtocolType.ICQ, gurrActivity);
		if (g != null)
			fGroupContacts = g;
	}
	
	public void writeHistory() {
		writer.writeHistory(fBufferHistory, gurrActivity);
	}
	
	
	public History readHistory() {
		if (fMapUinMyAccounts.getSelectedAccount() == null) {
			return null;
		}
		
		History h = reader.readHistory(fMapUinMyAccounts.getSelectedAccount().getUin(), gurrActivity);
		fCurrentHistory = h;
		return h;
	}
	
	@Deprecated
	private void parseGroup(Group gr){
		if(gr.getParentGroup() != null)
			fLstGroups.add(gr);
		Iterator<Group> git = gr.getSubgroups();
		
		while (git.hasNext()) {
			Group g = (Group) git.next();
			parseGroup(g);
		}
	}

    /** merge stored contact list w/ server list (sync contact names: prefer local stored names) */
	public void mergeContactLists(Group storedRootGroup, Group serverRootGroup) {
		
		fGroupContacts = serverRootGroup;
		
		/* just write contacts to xml storage */
		writeContactList();
		
		/* call contactactivity onContentChanged to update contact list */
		if (contactsActivity != null) {
			contactsActivity.rebuild();
		}
	}
	
	public void mergeContactLists(){
		mergeContactLists(fStoredRootGroup,fServerRootGroup);
	}

	public List<Group> getGroups() {
		fLstGroups.clear();
		parseGroup(fGroupContacts);
		return fLstGroups;
	}

	public void writeAccounts() {
		writer.writeAccounts(fMapUinMyAccounts, accountActivity);
	}

	public AccountMap getAccountMap() {
		return fMapUinMyAccounts;
	}

	public Group getfServerRootGroup() {
		return fServerRootGroup;
	}

	public void setfServerRootGroup(Group fServerRootGroup) {
		this.fServerRootGroup = fServerRootGroup;
	}

	public Group getfStoredRootGroup() {
		return fStoredRootGroup;
	}

	public void setfStoredRootGroup(Group fStoredRootGroup) {
		this.fStoredRootGroup = fStoredRootGroup;
	}
}

